//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//********************************************************* 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using Microsoft.TridentWordAddIn.Common;
using Microsoft.TridentWordAddIn.ResourceHelper;
using Microsoft.TridentWordAddIn.Server;
using System.Threading;
using System.Drawing;

namespace Microsoft.TridentWordAddIn.Presentation
{
    public partial class SelectWorkflowUserControl : BaseUserControl
    {
        private bool jobRetrieveTypeChanged;
        private string message;
        private Thread downloadJobs;
        private ICollection<WorkflowAssociation> workflowAssociationList;
        private bool isSearch;
        private string previousSearchString;

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectWorkflowUserControl"/> class.
        /// </summary>
        public SelectWorkflowUserControl()
        {
            InitializeComponent();
            this.workflowAssociationList = new List<WorkflowAssociation>();
        }

        private delegate void UICallerDelegate();

        #region EventHandler

        /// <summary>
        /// Occurs when [on selected job changed].
        /// </summary>
        public event EventHandler<SelectedJobChangedEventArgs> OnSelectedJobChanged;

        /// <summary>
        /// Occurs when [on job list update].
        /// </summary>
        public event EventHandler<JobsUpdatedEventArgs> OnJobsUpdated;

        /// <summary>
        /// Occurs when [on job double clicked].
        /// </summary>
        public event EventHandler<EventArgs> OnJobDoubleClicked;

        #endregion

        #region Public Methods

        /// <summary>
        /// Initializes the specified connection auto update.
        /// </summary>
        /// <param name="connectionAutoUpdate">if set to <c>true</c> [connection auto update].</param>
        public override void Initialize(bool connectionAutoUpdate)
        {
            try
            {
                base.Initialize(connectionAutoUpdate);
                this.ControllerInstance.OnConnectionChanged += new EventHandler<ConnectionChangedEventArgs>(Controller_OnConnectionChanged);
                this.ControllerInstance.OnDocumentClosing += new EventHandler<BaseEventArgs>(Controller_OnDocumentClosing);
                this.RefreshJobs(false);
            }
            catch (ConnectionFailedException connException)
            {
                MessageBoxHelper.ShowDefault(connException.Message, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Refreshes the jobs.
        /// </summary>
        public void RefreshJobs(bool onlyDocumentJob)
        {
            this.message = string.Empty;
            if (this.rbnDocument.Checked)
            {
                this.PopulateWorkflowFromDocument();
            }
            else if (!onlyDocumentJob)
            {
                this.PopulateWorkflowFromServer();
            }
        }

        /// <summary>
        /// Removes the event handlers.
        /// </summary>
        public void UnregisterEventHandlers()
        {
            this.ControllerInstance.OnConnectionChanged -= new EventHandler<ConnectionChangedEventArgs>(Controller_OnConnectionChanged);
            this.ControllerInstance.OnDocumentClosing -= new EventHandler<BaseEventArgs>(Controller_OnDocumentClosing);
            base.Disposing();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Populates the workflows.
        /// </summary>
        private void PopulateWorkflowFromServer()
        {
            this.downloadJobs.CancelThread();
            this.workflowAssociationList = new List<WorkflowAssociation>();
            this.lvJobs.Items.Clear();
            this.pnlJobs.Controls.Clear();
            
            if (this.Server.CurrentTridentConnection != null)
            {
                JobProcessing waitControl = new JobProcessing();
                waitControl.Dock = DockStyle.Fill;
                waitControl.Message = "Downloading Jobs...";
                this.pnlJobs.Controls.Add(waitControl);

                this.downloadJobs = new Thread(new ThreadStart(this.DownloadJobs));
                this.downloadJobs.Start();
                this.timerDownloadJobs.Start();
            }
            else
            {
                this.message = "No connection configured.";
                this.DisplayResult();
            }
        }

        /// <summary>
        /// Populates the workflows.
        /// </summary>
        private void PopulateWorkflowFromDocument()
        {
            this.downloadJobs.CancelThread();
            this.workflowAssociationList = new List<WorkflowAssociation>();
            this.workflowAssociationList = ControllerInstance.GetAllWorkflowAssociations(DocumentKey);
            this.DisplayResult();
        }

        /// <summary>
        /// Displays the result.
        /// </summary>
        private void DisplayResult()
        {
            int selectedIndex = this.lvJobs.SelectedIndices.Count == 0 || jobRetrieveTypeChanged ?
                                -1 : this.lvJobs.SelectedIndices[0];
            this.lvJobs.Items.Clear();
            this.pnlJobs.Controls.Clear();
            this.pnlJobs.Controls.Add(this.lvJobs);

            if (this.workflowAssociationList.Count == 0)
            {
                this.lvJobs.ShowErrorMessage(GetErrorMessage());
                this.OnJobListUpdation(0);
                return;
            }

            this.SetListViewColumns();
            string filter = string.Empty;
            if (this.isSearch)
            {
                filter = this.txtSearch.Text.Trim();
            }
            
            foreach (WorkflowAssociation association in this.workflowAssociationList)
            {
                if ((filter.IsNullOrEmpty() || association.Job.Name.ContainsIgnoreCase(filter)) && association.Job != null)
                {
                    ListViewItem newItem = this.lvJobs.Items.Add(association.Job.Name);
                    newItem.ImageKey = "job";
                    if (this.rbnDocument.Checked)
                    {
                        newItem.SubItems.Add(association.Name);
                    }

                    newItem.SubItems.Add(association.Job.DateSubmitted.ToString());
                    newItem.SubItems.Add(association.Job.DisplayId);
                    newItem.Tag = association;
                }
            }

            if (this.isSearch && lvJobs.Items.Count == 0)
            {
                this.lvJobs.ShowErrorMessage("No result matching your criteria found");
            }

            if (selectedIndex != -1)
            {
                lvJobs.Items[selectedIndex].Selected = true;
            }

            this.OnJobListUpdation(this.lvJobs.Items.Count);
        }

        /// <summary>
        /// Gets the error message.
        /// </summary>
        /// <returns></returns>
        private string GetErrorMessage()
        {
            if(this.message.IsNullOrEmpty())
            {
                if (rbnDocument.Checked)
                {
                    return "No jobs present in the document.";
                }

                return "No jobs present at the server.";
            }

            return this.message;
        }

        /// <summary>
        /// Sets the list view columns.
        /// </summary>
        private void SetListViewColumns()
        {
            this.lvJobs.Columns.Clear();
            string[] columns = null;
            if (this.rbnDocument.Checked)
            {
                columns = new string[] { "Job Name", "Association", "Creation Date", "Job Id" };
            }
            else
            {
                columns = new string[] { "Job Name", "Creation Date", "Job Id" };
            }

            this.lvJobs.AddColumns(columns);
        }

        /// <summary>
        /// Called when [job list updation].
        /// </summary>
        private void OnJobListUpdation(int count)
        {
            if (this.OnJobsUpdated != null)
            {
                JobsUpdatedEventArgs eventErgs = new JobsUpdatedEventArgs();
                eventErgs.Count = count;
                this.OnJobsUpdated(this, eventErgs);
            }
        }

        /// <summary>
        /// Downloads the jobs.
        /// </summary>
        private void DownloadJobs()
        {
            try
            {
                this.workflowAssociationList = new List<WorkflowAssociation>();
                List<WorkflowJob> jobs = Server.Jobs;
                if (jobs != null)
                {
                    jobs.Sort(new WorkflowJobComparer(SortOrder.Descending));
                    foreach (WorkflowJob job in jobs)
                    {
                        WorkflowAssociation workflowAssociation = new WorkflowAssociation();
                        workflowAssociation.Job = job;
                        this.workflowAssociationList.Add(workflowAssociation);
                    }
                }
            }
            catch (DatabaseException)
            {
                this.message = "Error occured during retrieving the jobs. Please check configuration.";
            }
            catch (ConnectionFailedException connException)
            {
                this.message = "Error occured during retrieving the jobs. " + connException.Message;
            }
            catch (ThreadAbortException)
            {
            }
            catch (Exception ex)
            {
                this.message = "Failed to retrieve jobs";
                if (ex.Message.ContainsIgnoreCase("Unknown web service failure") || 
                    ex.Message.ContainsIgnoreCase("Thread was being aborted") ||
                    ex.Message.ContainsIgnoreCase("Cannot connect to web service"))
                {
                    return;
                }

                this.BeginInvoke(new ShowMessageDelegate(MessageBoxHelper.ShowException), new object[] { ex, this});
            }
        }       

        #endregion

        #region Events Handlers

        /// <summary>
        /// Handles the OnConnectionChanged event of the Controller control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.ConnectionChangedEventArgs"/> instance containing the event data.</param>
        private void Controller_OnConnectionChanged(object sender, ConnectionChangedEventArgs e)
        {
            if (this.IsConnectionUpdateNeeded)
            {
                this.RefreshJobs(false);
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the lvWorkflows control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void lvJobs_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.lvJobs.SelectedIndices.Count > 0)
            {
                if (this.OnSelectedJobChanged != null)
                {
                    WorkflowJob job = null;
                    Workflow workflow = null;
                    WorkflowAssociation association = this.lvJobs.SelectedItems[0].Tag as WorkflowAssociation;
                    if (association != null)
                    {
                        job = association.Job;
                        workflow = rbnDocument.Checked ? 
                            this.ControllerInstance.WorkflowById(this.DocumentKey, association.WorkflowId) :
                            this.Server.GetWorkflowById(job.WorkflowId);
                    }

                    this.OnSelectedJobChanged(this, new SelectedJobChangedEventArgs(job, workflow));
                }
            }
        }

        /// <summary>
        /// Handles the MouseUp event of the lvJobs control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void lvJobs_MouseUp(object sender, MouseEventArgs e)
        {
            if (this.lvJobs.SelectedIndices.Count == 0)
            {
                this.OnSelectedJobChanged(this, new SelectedJobChangedEventArgs(null, null));
            }
        }

        /// <summary>
        /// Handles the DoubleClick event of the lvJobs control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void lvJobs_DoubleClick(object sender, EventArgs e)
        {
            if (this.OnJobDoubleClicked != null)
            {
                this.OnJobDoubleClicked(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Handles the Click event of the btnRefresh control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnRefresh_Click(object sender, EventArgs e)
        {
            this.isSearch = false;
            this.RefreshJobs(false);
        }

        /// <summary>
        /// Handles the Click event of the btnSearch control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnSearch_Click(object sender, EventArgs e)
        {
            if (!this.IsSearchStringChanged())
            {
                return;
            }

            this.isSearch = true;
            this.DisplayResult();
        }

        /// <summary>
        /// Handles the KeyPress event of the txtSearch control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.KeyPressEventArgs"/> instance containing the event data.</param>
        private void txtSearch_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Return)
            {
                if (!this.IsSearchStringChanged())
                {
                    return;
                }

                this.isSearch = true;
                this.DisplayResult();
            }
        }

        /// <summary>
        /// Determines whether [is search string changed].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is search string changed]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsSearchStringChanged()
        {
            string filter = this.txtSearch.Text.Trim();
            if (filter.CompareIgnoreCase(this.previousSearchString))
            {
                return false;
            }

            this.previousSearchString = filter;
            return true;
        }

        /// <summary>
        /// Handles the CheckedChanged event of the rbnDocument control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void rbnDocument_CheckedChanged(object sender, EventArgs e)
        {
            this.jobRetrieveTypeChanged = true;
            this.RefreshJobs(false);
            this.jobRetrieveTypeChanged = false;
        }

        /// <summary>
        /// Handles the Tick event of the timerDownloadJobs control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void timerDownloadJobs_Tick(object sender, EventArgs e)
        {
            if (!this.downloadJobs.IsAlive)
            {
                this.timerDownloadJobs.Stop();
                this.DisplayResult();
            }
        }

        /// <summary>
        /// Handles the OnDocumentClosing event of the Controller control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.BaseEventArgs"/> instance containing the event data.</param>
        private void Controller_OnDocumentClosing(object sender, BaseEventArgs e)
        {
            if (e.DocumentKey == DocumentKey)
            {
                downloadJobs.CancelThread();
            }
        }

        #endregion        
    }
}
